/*
*    Clean AJAX Engine v4.0
*    Copyright (C) 2005-2006 Carlos Eduardo Goncalves (cadu.goncalves@gmail.com)
*
*    This program is free software; you can redistribute it and/or modify
*    it under the terms of the GNU General Public License as published by
*    the Free Software Foundation; either version 2 of the License, or
*    (at your option) any later version.
*
*    This program is distributed in the hope that it will be useful,
*    but WITHOUT ANY WARRANTY; without even the implied warranty of
*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*    GNU General Public License for more details.
*
*    You should have received a copy of the GNU General Public License
*    along with this program; if not, write to the Free Software
*    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
*/

/**
* Provides a class used to read/write SOAP web services data streams.
* This implementation is focused on web service client requirements,
* it means that it is only able to write requests and read responses.
* This implementation supports only some XML Schema datatypes (integer, double, 
* string, boolean, datetime), SOAP-ENC-base64, SOAP-ENC:Array and structures. 
* This implementation does not support Soap Headers. 
* @author Carlos Eduardo Goncalves
*/

/**
* <p>Soap stream constructor.</p>
*/
function SoapStream(){
  /** <p>Register if there is a fault.</p> */	
  SoapStream.prototype.faultValue = undefined;
  /** <p>Store parsed object tree.</p> */	  
  SoapStream.prototype.tree = [];		
}

/** <p>Soap document prolog.</p> */
SoapStream.PROLOG = "<?xml version=\"1.0\"?>\n";

/** <p>Soap envelope node.</p> */
SoapStream.ENVELOPE = "<SOAP-ENV:Envelope SOAP-ENV:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\"" +
					  " xmlns:SOAP-ENC=\"http://schemas.xmlsoap.org/soap/encoding/\"" + 
					  " xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\"" +
					  " xmlns:xsd=\"http://www.w3.org/1999/XMLSchema\"" + 
					  " xmlns:xsi=\"http://www.w3.org/1999/XMLSchema-instance\">\n" +
					  "<SOAP-ENV:Body>\n${DATA}</SOAP-ENV:Body></SOAP-ENV:Envelope>";

/** <p>SOAP-ENC schema array node.</p> */
SoapStream.ARRAY = "<${NAME} xsi:type=\"SOAP-ENC:Array\" SOAP-ENC:arrayType=\"xsd:${TYPE}[${SIZE}]\">\n${DATA}</${NAME}>\n";

/** <p>SOAP-ENC schema base64 node.</p> */
SoapStream.BASE64 = "<${NAME} xsi:type=\"SOAP-ENC:base64\">\n${DATA}</${NAME}>\n";

/** <p>SOAP-ENC schema simple type nodes (int, double, string, boolean, dateTime).</p>*/
SoapStream.SOAP_VALUE = "<SOAP-ENC:${TYPE}>${DATA}</SOAP-ENC:${TYPE}>\n"

/** <p>XML Schema simple type nodes (integer, double, string, boolean, dateTime).</p> */
SoapStream.XSD_VALUE = "<${NAME} xsi:type=\"xsd:${TYPE}\">${DATA}</${NAME}>\n";

/** <p>Generic node.</p> */
SoapStream.GENERIC_VALUE = "<${NAME}>\n${DATA}</${NAME}>\n";


/**
* <p>Write a SOAP document.</p>
* @param method
*		<code>RemoteMethod</code> value object.
* @return
*		<code>XMLDocument</code> instance that is a valid SOAP document.
*/
SoapStream.prototype.write = function(method){
  try{		
    if(!Engine.assertType(method, RemoteMethod))
	  return null;
    var xml_params = "";	  
    for(var i = 0; i < method.params.length; i++)
      xml_params += this.serialize(method.params[i], null);	  
    var xml_method = SoapStream.GENERIC_VALUE;
	if(Engine.assertType(method.name, QName))
	  xml_method = xml_method.replace("${NAME}", method.name.toString());
	xml_method = xml_method.replace(/\$\{NAME\}/g, method.getName());			
    xml_method = xml_method.replace("${DATA}", xml_params);		
	var xml_call = SoapStream.PROLOG + SoapStream.ENVELOPE.replace("${DATA}", xml_method);
	Console.trace(xml_call, Console.RPC_DATA);
    var dom = (typeof DOMParser != "undefined") ? new DOMParser() : Engine.buildActiveX(Engine.ACTIVEX_DOM);		
    if(typeof DOMParser != "undefined")
      return dom.parseFromString(xml_call, "text/xml");    
    else{
	  dom.loadXML(xml_call);
      return dom;
	}
  } catch(e){Engine.reportException(null, e);}     
}

/**
* <p>Read a SOAP document.</p>
* @param doc
*		<code>XMLDocument</code> to read.
* @return
*		JavaScript object tree.
*/
SoapStream.prototype.read = function(doc){  
  this.faultValue = undefined;
  this.tree = [];
  if(doc)  
    for(var i = 0; i < doc.childNodes.length; i++)
      this.unserialize(doc.childNodes[i], 0);
  return this.tree[0];
}

/**
* <p>Encode a JavaScript object to a compatible SOAP representation.</p>
* @param data
*		JavaScript object to be encoded.
* @param name
*		<code>String</code> with an object attribute name or null.
* @return
*		<code>String</code> with SOAP object representation.
*/
SoapStream.prototype.serialize = function(data, name){
  try{
	var xml = "", node = "";
	if(typeof data == "object"){	
	  if(Engine.assertOneOfTypes(data, [String, Number, Date, Boolean])){
        if(name){
		  node = SoapStream.XSD_VALUE.replace(/\$\{NAME\}/g, name);
          node = node.replace(/\$\{TYPE\}/g, this.getDataType(data));
		}
		else
		  node = SoapStream.SOAP_VALUE.replace(/\$\{TYPE\}/g, this.getDataType(data));
	    node = node.replace(/\$\{DATA\}/g, (Engine.assertType(data, Date) == true) ? data.toIso8601() : data);		  		
		xml += node;
	  }
	  else
      if(Engine.assertType(data, Base64)){
        if(name)
		  node = SoapStream.BASE64.replace(/\$\{NAME\}/g, name); 
		else
		  node = SoapStream.SOAP_VALUE.replace(/\$\{TYPE\}/g, this.getDataType(data));
		node = node.replace(/\$\{DATA\}/g, data.encode());				
        xml += node;		  
	  }		  
	  else
      if(Engine.assertType(data, Array)){
		var array = "";
	    for(var element = 0; element < data.length; element++)
	      array += this.serialize(data[element], null);	
		if(name){    
	      node = SoapStream.ARRAY.replace(/\$\{NAME\}/g, name);
          node = node.replace(/\$\{TYPE\}/g, this.getDataType(data[0]));
          node = node.replace(/\$\{SIZE\}/g, data.length);	
		}
		else
		  node = SoapStream.SOAP_VALUE.replace(/\$\{TYPE\}/g, this.getDataType(data));
	    node = node.replace(/\$\{DATA\}/g, array);					
		xml += node;		  
      }   
      else{	
		for(var attribute in data){
		  if((typeof data[attribute] != "object") || 
			 (Engine.assertOneOfTypes(data[attribute], [String, Number, Date, Boolean, Base64, Array]))){			
			 xml += this.serialize(data[attribute], attribute);	
		  }
		  else{
            node = SoapStream.GENERIC_VALUE.replace(/\$\{NAME\}/g, attribute);	
            xml += node.replace(/\$\{DATA\}/g, this.serialize(data[attribute], attribute));			  
		  }
		}
	  }
	}	
	else{		
	  if(name){
	    node = SoapStream.XSD_VALUE.replace(/\$\{NAME\}/g, name);
		node = node.replace(/\$\{TYPE\}/g, this.getDataType(data));
	  }
	  else
		node = SoapStream.SOAP_VALUE.replace(/\$\{TYPE\}/g, this.getDataType(data));
      node = node.replace(/\$\{DATA\}/g, (Engine.assertType(data, Date) == true) ? data.toIso8601() : data);	
      xml += node;	
	}	
    return xml;
  } catch(e){Engine.reportException(null, e);}  
}

/**
* <p>Decode SOAP representation to a compatible JavaScript object.</p>
* @param node
*		<code>XMLDocument</code> current <code>Node</code>.
* @param parent
*		<code>XMLDocument</code> current parent <code>Node</code>.
* @return
*		JavaScript object tree. 
*/
SoapStream.prototype.unserialize = function(node, parent){ 
  if(node.nodeType == 1){
	var obj = null;
	var tag = node.tagName.toLowerCase();
    var type = node.getAttribute("xsi:type"); 	
    if(type == null){ 
	  if(tag.indexOf("soap-enc:") != -1)
        type = tag;
	  else	          
	    type = 'object';		
	}
    type = type.toLowerCase();
	if((type.indexOf("xsd:") != -1) || (type.indexOf("soap-enc:") != -1))
      type = type.split(":")[1];	
    this.propertyName = tag;	  
    switch(tag){ 
	  case 'soap-env:envelope':	  
	  case 'soap-env:body':
	    type = null;
		break;	
      case 'soap-env:fault':
	    this.faultValue = true; 
		break;	 
    }
    obj = this.buildObject(type);
	if(obj != null){
      this.tree.push(obj);	  
      if(type == 'object' || type == 'array'){
		if(this.tree.length > 1){  
          switch(this.getDataType(this.tree[parent])){		  
            case 'object': 	
              this.tree[parent][this.propertyName] = this.tree[this.tree.length - 1]; 
			  break;
            case 'array': 	 
              this.tree[parent].push(this.tree[this.tree.length - 1]); 
			  break;	 
          }		
		}
        var parent = this.tree.length - 1;		  	   
	  }
	}
    for(var i = 0; i < node.childNodes.length; i++){	
       this.unserialize(node.childNodes[i], parent);
    } 
  }
  if( (node.nodeType == 3) && (/[^\t\n\r ]/.test(node.nodeValue)) ){
    switch(this.getDataType(this.tree[this.tree.length - 1])){	   
	  case 'datetime':
	    this.tree[this.tree.length - 1] = Date.fromIso8601(node.nodeValue); 
	    break;
 	  case 'boolean':
		this.tree[this.tree.length - 1] = (node.nodeValue == "1") ? true : false; 
		break
 	  case 'int':
 	  case 'double':	
		this.tree[this.tree.length - 1] = new Number(node.nodeValue); 
		break
 	  case 'string':
		this.tree[this.tree.length - 1] = new String(node.nodeValue); 
		break
 	  case 'base64':
		this.tree[this.tree.length - 1] = new Base64(node.nodeValue); 
		break
    }
	if(this.tree.length > 1){  	  
      switch(this.getDataType(this.tree[parent])){		  
        case 'object': 	
          this.tree[parent][this.propertyName] = this.tree[this.tree.length - 1]; 
	      break;
        case 'array': 	 
          this.tree[parent].push(this.tree[this.tree.length - 1]); 
	  	  break;	 
      }
    }
  }
}

/**
* <p>Get the type used to represent a JavaScript
* object in the SOAP protocol.</p>
* @param data
*		A JavaScript object.
* @return
*		<code>String</code> with the object type. 
*/
SoapStream.prototype.getDataType = function(data){
  try{	
    var type = typeof data;	
    if(type == 'number')
     type = (Math.round(data) == data) ? "int" : "double";
    else  
    if(type == 'object'){
	  if(data.constructor == String)
	    type = "string";
	  else
	  if(data.constructor == Number)
	    type = (Math.round(data) == data) ? "int" : "double";
	  else	  
	  if(data.constructor == Date)
	    type = "datetime";
	  else	  
	  if(data.constructor == Boolean)
	    type = "boolean";
	  else
      if(data.constructor == Base64)
	    type = 'base64';
	  else
	  if(data.constructor == Array)
	    type = 'array';		  
    }	
    return type;
  } catch(e){Engine.reportException(null, e);}   
}

/**
* <p>Build a JavaScript object based in a SOAP type.</p>
* @param type
*		A <code>String </code> with the SOAP type.
* @return
*		The object created or null. 
*/
SoapStream.prototype.buildObject = function(type){ 
  var obj = null;
  switch(type){
    case 'object':
      obj = new Object(); 
	  break;
    case 'array':		
      obj = new Array(); 
	  break;
    case 'datetime':
      obj = new Date(); 
	  break;
    case 'boolean':
      obj = new Boolean(); 
	  break;
    case 'int':
    case 'double':
      obj = new Number(); 
	  break;	  
    case 'string':
      obj = new String(); 
	  break;	  
    case 'base64':
      obj = new Base64(); 
	  break;	    				
  }
  return obj;
}